﻿/**
 * @file circulardial.cpp
 * @brief 实现 CircularDial 类，绘制圆形仪表盘及其动画效果
 * @date 2025-08-07
 */

#include "circulardial.h"
#include <QPainter>
#include <QDebug>
#include <QtMath>
#include <QFontDatabase>
#include <QPainterPath>
#include <QPropertyAnimation>

#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8") // 设置字符编码为 UTF-8
#endif

/**
 * @brief 计算文本区域的矩形
 * @param radius 圆形半径
 * @param pointSize 字体大小
 * @param angle 角度
 * @return 文本区域的矩形
 */
static QRectF textRectF(double radius, int pointSize, double angle)
{
    QRectF rectF;                                                      // 定义矩形对象
    rectF.setX(radius * qCos(angle * M_PI / 180.0) - pointSize * 2);   // 计算 X 坐标
    rectF.setY(radius * qSin(angle * M_PI / 180.0) - pointSize / 2.0); // 计算 Y 坐标
    rectF.setWidth(pointSize * 4);                                     // 设置宽度
    rectF.setHeight(pointSize);                                        // 设置高度
    return rectF;                                                      // 返回矩形
}

/**
 * @brief 构造函数，初始化仪表盘
 * @param parent 父窗口指针
 */
CircularDial::CircularDial(QWidget* parent) : QWidget(parent)
{
    m_nLow = 30;             // 初始化低值阈值为 30
    m_nHigh = 120;           // 初始化高值阈值为 120
    m_nMax = 220;            // 初始化最大值为 220
    m_strFormat = "Km/h";    // 初始化格式化字符串为 Km/h
    m_bNeedRefreshBg = true; // 设置需要刷新背景

    setProperty("DialValue", 0.0);                                                             // 设置初始仪表盘值为 0
    m_pAnimationOpacity = new QPropertyAnimation(this, "DialValue");                           // 创建动画对象
    connect(m_pAnimationOpacity, SIGNAL(valueChanged(const QVariant&)), this, SLOT(update())); // 连接动画信号到更新槽
}

/**
 * @brief 重写绘制事件，绘制仪表盘
 * @param event 绘制事件对象
 */
void CircularDial::paintEvent(QPaintEvent* event)
{
    QPainter p(this);                                                                             // 创建绘图对象
    QRect rcClient = rect();                                                                      // 获取客户区域
    int nLen = qMin(rcClient.width(), rcClient.height() - 20);                                    // 计算最小边长
    QRect rcBase(rcClient.center().x() - nLen / 2, rcClient.center().y() - nLen / 2, nLen, nLen); // 定义基础矩形

    p.setRenderHint(QPainter::Antialiasing);          // 启用抗锯齿
    p.setRenderHint(QPainter::SmoothPixmapTransform); // 启用平滑图像变换

    paintBg(p); // 绘制背景

    ///< 下面开始绘制仪表盘的刻度数字和表盘指针

    p.translate(rcBase.center()); // 平移到中心

    int side = qMin(rcBase.width(), rcBase.height()); // 获取最小边
    p.scale(side / 300.0, side / 300.0);              // 缩放画布
    QRect rcCircularDial(-150, -150, 300, 300);       // 定义仪表盘矩形

    const double START_ANGLE = -60; // 定义起始角度

    p.save();                        // 保存画笔状态
    QFont font1 = font();            // 获取当前字体
    font1.setBold(true);             // 设置字体加粗
    font1.setPixelSize(26);          // 设置字体大小
    p.setFont(font1);                // 应用字体
    if (m_fDialValue < m_nLow)       // 如果值低于低值阈值
        p.setPen(QColor(0x238a23));  // 设置绿色画笔
    else if (m_fDialValue < m_nHigh) // 如果值在正常范围内
        p.setPen(QColor(0x4687c1));  // 设置蓝色画笔
    else                             // 如果值高于高值阈值
        p.setPen(QColor(0xff4400));  // 设置红色画笔

    QRect rcValue(-60, 60, 120, 50); // 定义值显示区域
    p.drawText(rcValue, Qt::AlignCenter,
               QString("%1%2").arg(QString::number(m_fDialValue, 'f', 0)).arg(m_strFormat)); // 绘制值文本
    p.restore();                                                                             // 恢复画笔状态

    p.save();                                                    // 保存画笔状态
    p.rotate(180 + START_ANGLE);                                 // 旋转到起始角度
    p.rotate((180 - 2 * (START_ANGLE)) / m_nMax * m_fDialValue); // 根据值旋转

    QPainterPath path;                                  // 创建绘图路径
    QPolygon triangle;                                  // 定义三角形
    triangle.setPoints(4, 110, 0, 0, -5, -10, 0, 0, 5); // 设置三角形顶点
    path.addPolygon(triangle);                          // 添加三角形到路径
    p.setPen(Qt::NoPen);                                // 设置无画笔
    p.setBrush(QColor(0x1f901a));                       // 设置绿色画刷
    p.drawPath(path);                                   // 绘制路径
    p.restore();                                        // 恢复画笔状态

    p.save();                                     // 保存画笔状态
    p.setPen(Qt::NoPen);                          // 设置无画笔
    p.setBrush(Qt::white);                        // 设置白色画刷
    p.drawEllipse(rcCircularDial.center(), 2, 2); // 绘制中心点
    p.restore();                                  // 恢复画笔状态
}

/**
 * @brief 绘制仪表盘背景
 * @param painter 绘图对象
 */
void CircularDial::paintBg(QPainter& painter)
{
    QRect rcClient = rect(); // 获取客户区域

    if (m_pixBg.isNull() || m_pixBg.size() != rcClient.size() || m_bNeedRefreshBg) // 如果背景无效或需要刷新
    {
        m_pixBg = QPixmap(rcClient.size()); // 创建背景图像
        m_pixBg.fill(Qt::transparent);      // 填充透明背景

        QPainter p(&m_pixBg);                                                                         // 在背景图像上绘图
        int nLen = qMin(rcClient.width(), rcClient.height() - 20);                                    // 计算最小边长
        QRect rcBase(rcClient.center().x() - nLen / 2, rcClient.center().y() - nLen / 2, nLen, nLen); // 定义基础矩形

        p.setRenderHint(QPainter::Antialiasing);          // 启用抗锯齿
        p.setRenderHint(QPainter::SmoothPixmapTransform); // 启用平滑图像变换

        p.translate(rcBase.center()); // 平移到中心

        int side = qMin(rcBase.width(), rcBase.height()); // 获取最小边
        p.scale(side / 300.0, side / 300.0);              // 缩放画布
        QRect rcCircularDial(-150, -150, 300, 300);       // 定义仪表盘矩形

        const double START_ANGLE = -60;                              // 定义起始角度
        const double ALL_ANGLE = 360 - 2 * (90 + START_ANGLE);       // 计算总角度
        const double BIG_SINGLE_ANGLE = ALL_ANGLE / (m_nMax / 10.0); // 计算大刻度角度
        const double SMALL_SINGLE_ANGLE = BIG_SINGLE_ANGLE / 10.0;   // 计算小刻度角度

        p.save(); // 保存画笔状态

        ///< 绘制弧线区域
        // 绘制高值区域 [100,80)
        int startAngle = START_ANGLE * 16;                                // 计算起始角度
        int spanAngle = SMALL_SINGLE_ANGLE * (m_nMax - m_nHigh) * 16;     // 计算偏移角度
        p.setPen(QPen(QColor(0xff4400), 30, Qt::SolidLine, Qt::FlatCap)); // 设置红色画笔
        p.drawArc(rcCircularDial, startAngle, spanAngle);                 // 绘制弧线

        // 绘制正常区域 [80,20]
        startAngle = (START_ANGLE + SMALL_SINGLE_ANGLE * (m_nMax - m_nHigh)) * 16; // 更新起始角度
        spanAngle = SMALL_SINGLE_ANGLE * (m_nHigh - m_nLow) * 16;                  // 计算偏移角度
        p.setPen(QPen(QColor(0x4687c1), 30, Qt::SolidLine, Qt::FlatCap));          // 设置蓝色画笔
        p.drawArc(rcCircularDial, startAngle, spanAngle);                          // 绘制弧线

        // 绘制低值区域 (20,0]
        startAngle = (START_ANGLE + SMALL_SINGLE_ANGLE * (m_nMax - m_nLow)) * 16; // 更新起始角度
        spanAngle = SMALL_SINGLE_ANGLE * m_nLow * 16;                             // 计算偏移角度
        p.setPen(QPen(QColor(0x238a23), 30, Qt::SolidLine, Qt::FlatCap));         // 设置绿色画笔
        p.drawArc(rcCircularDial, startAngle, spanAngle);                         // 绘制弧线
        p.restore();                                                              // 恢复画笔状态

        p.save(); // 保存画笔状态

        ///< 绘制弧线区域内的颜色
        p.rotate(90 + START_ANGLE); // 旋转到起始角度
        // 绘制低值区域刻度 [0,20]
        int i = 0;                        // 循环计数器
        for (i = 0; i < m_nLow / 10; i++) // 遍历低值区域
        {
            p.save();                    // 保存画笔状态
            for (int j = 0; j < 10; j++) // 绘制小刻度
            {
                p.setPen(QPen(QColor(0x238a23), 1, Qt::SolidLine, Qt::FlatCap));               // 设置绿色画笔
                p.drawLine(0, rcCircularDial.width() / 2 - 10, 0, rcCircularDial.width() / 2); // 绘制小刻度线
                p.rotate(SMALL_SINGLE_ANGLE);                                                  // 旋转到下一个小刻度
            }
            p.restore(); // 恢复画笔状态

            p.setPen(QPen(QColor(0x238a23), 2, Qt::SolidLine, Qt::FlatCap));                   // 设置绿色画笔
            p.drawLine(0, rcCircularDial.width() / 2 - 20, 0, rcCircularDial.width() / 2 + 5); // 绘制大刻度线
            p.rotate(BIG_SINGLE_ANGLE);                                                        // 旋转到下一个大刻度
        }

        // 绘制正常区域刻度 [80,20]
        for (; i < m_nHigh / 10; i++) // 遍历正常区域
        {
            p.save();                    // 保存画笔状态
            for (int j = 0; j < 10; j++) // 绘制小刻度
            {
                p.setPen(QPen(QColor(0x4687c1), 1, Qt::SolidLine, Qt::FlatCap));               // 设置蓝色画笔
                p.drawLine(0, rcCircularDial.width() / 2 - 10, 0, rcCircularDial.width() / 2); // 绘制小刻度线
                p.rotate(SMALL_SINGLE_ANGLE);                                                  // 旋转到下一个小刻度
            }
            p.restore(); // 恢复画笔状态

            p.setPen(QPen(QColor(0x4687c1), 2, Qt::SolidLine, Qt::FlatCap));                   // 设置蓝色画笔
            p.drawLine(0, rcCircularDial.width() / 2 - 20, 0, rcCircularDial.width() / 2 + 5); // 绘制大刻度线
            p.rotate(BIG_SINGLE_ANGLE);                                                        // 旋转到下一个大刻度
        }

        // 绘制高值区域刻度 [80,20]
        for (; i <= m_nMax / 10; i++) // 遍历高值区域
        {
            p.save();                                       // 保存画笔状态
            for (int j = 0; j < 10 && i < m_nMax / 10; j++) // 绘制小刻度
            {
                p.setPen(QPen(QColor(0xff4400), 1, Qt::SolidLine, Qt::FlatCap));               // 设置红色画笔
                p.drawLine(0, rcCircularDial.width() / 2 - 10, 0, rcCircularDial.width() / 2); // 绘制小刻度线
                p.rotate(SMALL_SINGLE_ANGLE);                                                  // 旋转到下一个小刻度
            }
            p.restore(); // 恢复画笔状态

            p.setPen(QPen(QColor(0xff4400), 2, Qt::SolidLine, Qt::FlatCap));                   // 设置红色画笔
            p.drawLine(0, rcCircularDial.width() / 2 - 20, 0, rcCircularDial.width() / 2 + 5); // 绘制大刻度线
            p.rotate(BIG_SINGLE_ANGLE);                                                        // 旋转到下一个大刻度
        }
        p.restore(); // 恢复画笔状态

        p.save(); // 保存画笔状态

        ///< 绘制表盘当前值
        QFont ftTmp1 = p.font();               // 获取当前字体
        ftTmp1.setBold(true);                  // 设置字体加粗
        ftTmp1.setPixelSize(15);               // 设置字体大小
        p.setFont(ftTmp1);                     // 应用字体
        for (int i = 0; i <= m_nMax / 10; i++) // 遍历所有大刻度
        {
            if (i < m_nLow / 10)            // 如果在低值区域
                p.setPen(QColor(0x238a23)); // 设置绿色画笔
            else if (i < m_nHigh / 10)      // 如果在正常区域
                p.setPen(QColor(0x4687c1)); // 设置蓝色画笔
            else                            // 如果在高值区域
                p.setPen(QColor(0xff4400)); // 设置红色画笔

            p.drawText(textRectF(150 * 0.8, ftTmp1.pixelSize() + 5, 180 + START_ANGLE + i * BIG_SINGLE_ANGLE),
                       Qt::AlignCenter,
                       QString::number(i * 10)); // 绘制刻度值
        }
        p.restore(); // 恢复画笔状态
        p.end();     // 结束背景绘制

        m_bNeedRefreshBg = false; // 标记背景无需刷新
    }

    painter.drawPixmap(0, 0, m_pixBg); // 绘制背景图像
}

/**
 * @brief 获取当前仪表盘值
 * @return 当前仪表盘值
 */
float CircularDial::fDialValue() const
{
    return m_fDialValue; // 返回当前值
}

/**
 * @brief 设置仪表盘值
 * @param fDialValue 新的仪表盘值
 */
void CircularDial::setFDialValue(float fDialValue)
{
    m_fDialValue = fDialValue; // 设置新值
    emit DialValueChanged();   // 加上这一句后 NOTIFY 就生效
    update();                  // 触发重绘
}

/**
 * @brief 获取格式化字符串
 * @return 当前格式化字符串
 */
QString CircularDial::strFormat() const
{
    return m_strFormat; // 返回格式化字符串
}

/**
 * @brief 设置格式化字符串
 * @param strFormat 新的格式化字符串
 */
void CircularDial::setStrFormat(const QString& strFormat)
{
    m_strFormat = strFormat; // 设置新格式化字符串
    m_bNeedRefreshBg = true; // 标记需要刷新背景
    update();                // 触发重绘
}

/**
 * @brief 激活仪表盘动画
 */
void CircularDial::active()
{
    m_pAnimationOpacity->stop(); // 停止当前动画

    float DialValue = property("DialValue").toFloat();                      // 获取当前值
    m_pAnimationOpacity->setDuration(2000 * (m_nMax - DialValue) / m_nMax); // 设置动画持续时间
    m_pAnimationOpacity->setEasingCurve(QEasingCurve::InOutQuad);           // 设置缓动曲线
    m_pAnimationOpacity->setStartValue(DialValue);                          // 设置起始值
    m_pAnimationOpacity->setEndValue(m_nMax);                               // 设置结束值
    m_pAnimationOpacity->start();                                           // 启动动画
}

/**
 * @brief 将仪表盘恢复到正常状态
 */
void CircularDial::normal()
{
    m_pAnimationOpacity->stop(); // 停止当前动画

    float DialValue = property("DialValue").toFloat();           // 获取当前值
    m_pAnimationOpacity->setDuration(2000 * DialValue / m_nMax); // 设置动画持续时间
    m_pAnimationOpacity->setEasingCurve(QEasingCurve::Linear);   // 设置缓动曲线
    m_pAnimationOpacity->setStartValue(DialValue);               // 设置起始值
    m_pAnimationOpacity->setEndValue(0);                         // 设置结束值
    m_pAnimationOpacity->start();                                // 启动动画
}

/**
 * @brief 获取最大值
 * @return 当前最大值
 */
int CircularDial::nMax() const
{
    return m_nMax; // 返回最大值
}

/**
 * @brief 设置最大值
 * @param nMax 新的最大值
 */
void CircularDial::setNMax(int nMax)
{
    m_nMax = nMax;           // 设置新最大值
    m_bNeedRefreshBg = true; // 标记需要刷新背景
    update();                // 触发重绘
}

/**
 * @brief 获取高值阈值
 * @return 当前高值阈值
 */
int CircularDial::nHigh() const
{
    return m_nHigh; // 返回高值阈值
}

/**
 * @brief 设置高值阈值
 * @param nHigh 新的高值阈值
 */
void CircularDial::setNHigh(int nHigh)
{
    m_nHigh = nHigh;         // 设置新高值阈值
    m_bNeedRefreshBg = true; // 标记需要刷新背景
    update();                // 触发重绘
}

/**
 * @brief 获取低值阈值
 * @return 当前低值阈值
 */
int CircularDial::nLow() const
{
    return m_nLow; // 返回低值阈值
}

/**
 * @brief 设置低值阈值
 * @param nLow 新的低值阈值
 */
void CircularDial::setNLow(int nLow)
{
    m_nLow = nLow;           // 设置新低值阈值
    m_bNeedRefreshBg = true; // 标记需要刷新背景
    update();                // 触发重绘
}
